﻿# CoffeeScript
if not window.Moshu
    window.Moshu = new Object()
window.Moshu.Canvas2D = new Object()

class Moshu.Canvas2D.Vector
    constructor : (x,y) ->
        @arr = [x,y]
        @isVector = true
    x : (x) ->
        if x
            @arr[0] = x
        @arr[0]
    y : (y) ->
        if y
            @arr[1] = y
        @arr[1]
    substract : (vector) ->
        new Vector(@x() - vector.x(), @y() - vector.y())
    add : (vector) ->
        new Vector(@x() + vector.x(), @y() + vector.y())
    dotProduct : (vector)->
        @x()*vector.x()+@y()*vector.y()    
    getPerpendicular : ()->
        new Vector(@y(),-@x())
    getMagnitude : () ->
        Math.sqrt(@x()*@x() + @y()*@y())
        
class window.Moshu.Canvas2D.PhysicalObject extends Moshu.Observable
    constructor : () ->
        super
        #no mass
        @mass = 0
        #no elasticity
        @elasticity = 0
        #no friction with air
        @mediumFrictionCoef = 0
        #no friction with other objects
        @objectFrictionCoef = 0
        # no speed
        @_velocity = new Moshu.Canvas2D.Vector(0,0)            
        # no acceleration
        @_acceleration = new Moshu.Canvas2D.Vector(0,0)                
        #does not move
        @fixed = false
        @isPhysicalObject = true
        #specifies whether other shapes can get through it, or it can go through other shapes
        @isSolid = false
    velocity : (val)->
        if val
            if !@fixed
                if val.isVector            
                    @_velocity = val
                else
                    @log("The parameter needs to be a Vector. (Moshu.Canvas2D.Vector object)")
            else 
                @log("Cannot change the velocity of a fixed object. Try setting fixed attribute to false first.")
        @_velocity
    acceleration : (val)->
        if val
            if !@fixed
                if val.isVector            
                    @_acceleration = val
                else
                    @log("The parameter needs to be a Vector. (Moshu.Canvas2D.Vector object)")
            else 
                @log("Cannot change the acceleration of a fixed object. Try setting fixed attribute to false first.")
        @_acceleration
    getMomentum : () ->        
        new Moshu.Canvas2D.Vector(@mass * @_velocity.x(), @mass * @_velocity.y())
        
class window.Moshu.Canvas2D.PositionedObject extends Moshu.Canvas2D.PhysicalObject
    constructor : () ->
        super
        @isPositionedObject = true
    move : (vector)->
        throw "Not implemented"    
    getCollision : (obj)->
        throw "Not implemented"
    manageCollision : (obj)->
        throw "Not implemented"
    
class window.Moshu.Canvas2D.Polygon extends Moshu.Canvas2D.PositionedObject
    constructor : (vertices) ->
        super
        @isPolygon = true
        @_vertices = []
        if vertices and vertices.length
            for vertex in vertices
                if vertex and vertex.isVector
                    @_vertices.push(vertex)
                else
                    @log("Vertex is undefined or is not a vector. Vertex ignored...")
        else @log("Provided vertices is not an array. You need to supply an array.")
        @_savedVertices = null;
    save : ()->
        @_savedVertices = [];
        for v in @_vertices
            @_savedVertices.push(new Moshu.Canvas2D.Vector(v.x(),v.y()))
    restore: () ->
        if @_savedVertices
            @_vertices = []
            for v in @_savedVertices
                @_vertices.push(new Moshu.Canvas2D.Vector(v.x(),v.y()))
    getVertices : () ->
        @_vertices
    move : (vector) ->
        if vector and vector.isVector
            if vector.x()!=0 or vector.y()!=0
                for v,i in @_vertices
                    @_vertices[i] = v.add(vector)
        else
            @log("Supplied argument of move command is not a vector.") 
        # rotate the polygon by the angle with respect to a point called rotationCenter
    # if rotation center is not supplied, the object will rotate by its center of mass
    rotate : (angle,rotationCenter) ->
        if angle and angle != 0
            centerX = rotationCenter.x()
            centerY = rotationCenter.y()
            if centerX and centerY
                #rotation = new Moshu.Canvas2D.Rotation(centerX,centerY,angle)
                cosAngle = Math.cos(angle );
                sinAngle = Math.sin(angle );
                for vertex in @_vertices                  
                    dx = (vertex.x()-centerX);
                    dy = (vertex.y()-centerY);
                    vertex.x(centerX + dx*cosAngle-dy*sinAngle)
                    vertex.y(centerY + dx*sinAngle+dy*cosAngle)                                
    getSidesVectors: ()->
        arr = []
        for v,i in @_vertices
            v1 = @_vertices[i]
            v2 = null
            if i is @_vertices.length-1
                v2 = @_vertices[0]
            else
                v2 = @_vertices[i+1]
            sideVector = v1.substract(v2)
            arr.push(sideVector)
        arr
    getProjection:(axis)->
        vertices = @_vertices
        min = axis.dotProduct(vertices[0])
        max = min        
        for v,i in vertices            
            val = axis.dotProduct(v)
            if min>val
                min = val
            if max<val
                max = val
        [min,max]
    getCollision : (polygon) ->        
        if polygon.isPolygon
            sides1 = @getSidesVectors()
            sides2 = polygon.getSidesVectors()
            free = false
            
            collision = 
                collider : this
                collided : polygon
                recoveryVector : null
                recoveryMagnitude : null
            # check for the first polygon
            for side in sides1
                axis = side.getPerpendicular()
                p1 = @getProjection(axis)
                p2 = polygon.getProjection(axis)                
                #check if p1 and p2 intersect
                overlap = false
                mag = 0
                if (p1[0]>p2[0] and p1[0] < p2[1])
                    overlap = true
                    mag = p2[1]-p1[0]
                if (p1[1]>p2[0] and p1[1] < p2[1])
                    overlap = true
                    mag = p2[1]-p1[0]
                if (p2[0]>p1[0] and p2[0] < p1[1])
                    overlap = true
                    mag = p1[1]-p2[0]
                if (p2[1]>p1[0] and p2[1] < p1[1])
                    overlap = true
                    mag = p1[1]-p2[0]
                if (p1[0]==p2[0] and p1[1] == p2[1])
                    overlap = true
                    mag = p1[1]-p2[0]
                
                #if the projections do not overlap, the shapes do not intersect
                if not overlap
                    free = true
                else
                    if mag<collision.recoveryMagnitude or !collision.recoveryMagnitude
                        collision.recoveryMagnitude = mag
                        collision.recoveryVector = axis
                        
            # check for the second polygon
            for side in sides2
                axis = side.getPerpendicular()
                p1 = @getProjection(axis)
                p2 = polygon.getProjection(axis)                
                #check if p1 and p2 intersect
                overlap = false
                mag = 0
                if (p1[0]>=p2[0] and p1[0] <= p2[1])
                    overlap = true
                    mag = p2[1]-p1[0]
                if (p2[0]>=p1[0] and p2[0] <= p1[1])
                    overlap = true
                    mag = p1[1]-p2[0]
                
                #if the projections do not overlap, the shapes do not intersect
                if not overlap
                    free = true
                else                
                    if mag<collision.recoveryMagnitude or !collision.recoveryMagnitude                        
                        collision.recoveryMagnitude = mag
                        collision.recoveryVector = axis
            if free
                null
            else 
                if collision.recoveryVector
                    collision.recoveryMagnitude = collision.recoveryMagnitude / collision.recoveryVector.getMagnitude()
                collision
        else
            @log("getCollision requires a Polygon attribute.")
            null
    getBoundary : () ->
        if @_vertices and @_vertices.length
            minx = null
            maxx = null
            miny = null
            maxy = null
            for elem in @_vertices
                if elem.x()<minx or not minx 
                    minx = elem.x()
                if elem.x()>maxx or not maxx 
                    maxx = elem.x()
                if elem.y()<miny or not miny 
                    miny = elem.y()
                if elem.y()>maxy or not maxy 
                    maxy = elem.y()
            [minx,miny,maxx,maxy]
        else
            [0,0,0,0]           
# constructs a regular poligon    
class window.Moshu.Canvas2D.RegularPolygon extends Moshu.Canvas2D.Polygon
    constructor : (x,y,radius,rank)->    
        vertices = []        
        for i in [0..rank-1] by 1
            x = ( Math.sin( i / rank * 2 * Math.PI ) * radius ) + x
            y = ( Math.cos( i / rank * 2 * Math.PI ) * radius ) + y
            vertex = new Moshu.Canvas2D.Vector(x,y)
            vertices.push(vertex)
        super(vertices)
        
# classes for creating simple polygons    
class window.Moshu.Canvas2D.Line extends Moshu.Canvas2D.Polygon
    constructor : (x,y, width, height) ->
        super ([[x,y],[x+width, y+height]])
        @isClosed = false        

        
# constructs a Triangle
class window.Moshu.Canvas2D.Triangle extends Moshu.Canvas2D.Polygon
    constructor : (x1,y1,x2,y2,x3,y3)->
        super ([[x1,y1],[x2, y2],[x3, y3]])

# constructs a rectangle
class window.Moshu.Canvas2D.Rectangle extends Moshu.Canvas2D.Polygon
    constructor : (@x,@y,@width,@height)->
        super ([
            new Moshu.Canvas2D.Vector(@x,@y),
            new Moshu.Canvas2D.Vector(@x+@width, @y),
            new Moshu.Canvas2D.Vector(@x+@width, @y+@height),
            new Moshu.Canvas2D.Vector(@x, @y+@height)
            ])
            
# constructs a rectangle
class window.Moshu.Canvas2D.CanvasImage extends Moshu.Canvas2D.Rectangle
    constructor : (@src,@x,@y,@width,@height)->
        @isImage = true
        super(@x,@y,@width,@height)
        @image= new Image()
        @image.src = @src
    recomputePosition : () ->
        vertices = @_vertices
        minx = vertices[0].x()
        miny = vertices[0].y()
        for v,i in vertices
            if v.x()<minx 
                minx = v.x()
            if v.y()<miny
                miny = v.y()
        @x = minx
        @y = miny   
    rotate : (angle, rotationCenter) ->
        super(angle, rotationCenter)        
        @recomputePosition()
    move : (vector) ->
        super(vector)        
        @recomputePosition()
    save : ()->
        super()
        @recomputePosition()
    restore: () ->
        super()
        @recomputePosition()
class window.Moshu.Canvas2D.Painter
    constructor : ()->    
        @isPainter  = true
           
class window.Moshu.Canvas2D.PolygonPainter extends Moshu.Canvas2D.Painter
    constructor : (@context, @polygon) ->
        super
        @fill = 
            color : "#FFF",
            enabled : false
        @stroke = 
            width : 1,
            color : "#000",
            enabled : true        
    draw : () ->        
        ctx = @context
                       
        if @stroke.enabled
            ctx.lineWidth = @stroke.width
            ctx.strokeStyle = @stroke.color
        else 
            ctx.lineWidth = 0
            
        if @fill.enabled			
            ctx.fillStyle = @fill.color
        else
            ctx.fillStyle = null
        ctx.beginPath()        
        vertices = @polygon.getVertices()
        for vertex, i in vertices
            if i is 0
                ctx.moveTo(vertex.x(),vertex.y())
            else
                ctx.lineTo(vertex.x(),vertex.y())
                
        #if vertices.length > 2     
            #ctx.lineTo(vertices[vertices.length-1].x,vertices[vertices.length-1].x)
            #ctx.lineTo(vertices[0].x(),vertices[0].x())                    
        ctx.closePath()   
        if @stroke.enabled
                ctx.stroke()
        if @fill.enabled
            ctx.fill()
            
class window.Moshu.Canvas2D.ImagePainter extends Moshu.Canvas2D.Painter
    constructor : (@context, @image) ->
        super
    draw : ()->
        ctx = @context
        ctx.drawImage(@image.image,@image.x,@image.y,@image.width,@image.height)
        
class window.Moshu.Canvas2D.World extends Moshu.Observable
    constructor : (@id) ->
        super
        @canvas = document.getElementById(@id)                
        if @canvas
            @context = @canvas.getContext("2d")
            @width = @canvas.width
            @height = @canvas.height
        else            
            @context = null
        if !@context
            throw "Invalid attribute supplied to the World object. Canvas could not be found or accessed."
        @shapes = []
        window.Moshu.AnimationManager.bind(this, @timeUpdate)
        @lastUpdate = null
        @frozen = false
    createShape : (physicalObject, objectPainter)->
        if physicalObject and objectPainter and physicalObject.isPhysicalObject and objectPainter.isPainter
            physicalObject.painter = objectPainter
            @shapes.push(physicalObject)
        else
            @logError("Invalid attributes in createShape function. The first argument must be a PhysicalObject, and the second must be a Painter.")
    createShapeFromPolygon : (polygon)->
        if polygon and polygon.isPolygon
            @createShape(polygon, new Moshu.Canvas2D.PolygonPainter(@context, polygon))
        else
            @logError("Invalid attributes in createShapeFromPolygon function. The first argument must be a Polygon, and the second must be a Painter.")
    createShapeFromImage : (image)->
        if image and image.isImage
            @createShape(image, new Moshu.Canvas2D.ImagePainter(@context, image))
        else
            @logError("Invalid attributes in createShapeFromImage function. The first argument must be an Image, and the second must be a Painter.")
    remove : (shape)->
        for s,i in @shapes
            if (s is shape)
                @shapes.splice(i,1)
                break                
    clear : () ->
        @shapes = []
    freeze : ()->
        @fronzen = true   
        @frozen     
    unfreeze : ()->
        @fronzen = false        
        @frozen
    refresh : () ->
        if !@frozen
            @context.clearRect(0,0, @width, @height)
            for shape in @shapes
                if shape and shape.painter
                    shape.painter.draw()                        
    supressTimer : () ->        
        window.Moshu.AnimationManager.unbind(@timeUpdate)
            
    timeUpdate : (dt)->         
        for shape in @shapes
            if shape and not shape.fixed
                # compute new position                
                dt = dt/1000
                velocityIncreaseVector = new Moshu.Canvas2D.Vector(shape.acceleration().x()*dt, shape.acceleration().y()*dt)
                shape.velocity(shape.velocity().add(velocityIncreaseVector))                
                distance = new Moshu.Canvas2D.Vector(shape.velocity().x()*dt, shape.velocity().y())                
                shape.move(distance)                
                
                # check for collisions               
                for s in @shapes
                    if not (s is shape)
                        c = shape.getCollision(s)
                        if c                                                      
                            shape.manageCollision(c)                
        @refresh()
          

